package com.artfii.fluentsql.core.tools;


import com.artfii.fluentsql.core.exception.FluentSqlException;
import com.artfii.fluentsql.core.tools.cache.Cache;
import com.artfii.fluentsql.core.tools.cache.CacheManager;
import org.joda.time.DateTime;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;

import static java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME;

/**
 * Func : POJO <--> MAP 复制
 *
 * @author: leeton on 2019/6/14.
 */
public class BeanMapUtils {
    private static boolean skip_null = true;  //跳过空值
    private static boolean skip_transient = true;  //跳过临时字段
    private static boolean skip_json_ignore = false;  //跳过 JsonIgnore
    private static boolean skip_json_reference = false;  //跳过 JsonBackReference
    private static boolean spell_fuzzy_match = false; //模糊匹配的模式
    private static boolean to_underline = false; // 转为下划线
    private static boolean to_camel = false; // 转为驼峰
    private static boolean cache_pojo = true; // 是否缓存对象结构
    private static boolean format_val = false; //源的值是否进行格式的转换

    public static <T> T copyTo(Object source, T target, String... ignList) {
        copy(source, target, ignList);
        return (T) target;
    }

    public static BeanMapUtils builder(){
        BeanMapUtils b = new BeanMapUtils();
        b.setFormatVal(true);//兼容性优先原则,默认做格式转换
        b.setSkipNullVal(true);
        return b;
    }

    public static BeanMapUtils fuzzy(){
        return BeanMapUtils.builder().setSpellFuzzyMatch(true);
    }

    public BeanMapUtils setSkipNullVal(boolean tf) {
        this.skip_null = tf ;
        return this;
    }
    public BeanMapUtils setSkipTransient(boolean tf) {
        this.skip_transient = tf ;
        return this;
    }
    public BeanMapUtils setSkipJsonIgnore(boolean tf) {
        this.skip_json_ignore = tf ;
        return this;
    }
    public BeanMapUtils setSkipJsonRef(boolean tf) {
        this.skip_json_reference= tf ;
        return this;
    }
    public BeanMapUtils setSpellFuzzyMatch(boolean tf) {
        this.spell_fuzzy_match = tf;
        return this;
    }
    public BeanMapUtils setToUnderline(boolean tf) {
        this.to_underline = tf;
        return this;
    }
    public BeanMapUtils toUnderline() {
        this.to_underline = true;
        return this;
    }
    public BeanMapUtils toCamel() {
        this.to_camel = true;
        return this;
    }
    public BeanMapUtils notCache() {
        this.cache_pojo = false;
        return this;
    }

    public BeanMapUtils setFormatVal(boolean tf) {
        this.format_val = tf;
        return this;
    }

    public void c(Object source, Object target, String... ignList){
        if (null == source || target == null) {//为空,则不进行COPY属性
            return;
        }
        if (target instanceof Map) {
            toMap(source, (Map<String, Object>) target, ignList);
            return;
        }
        if (source instanceof Map) {
            fromMap((Map<String, Object>) source, target, ignList);
            return;
        }
        toPojo(source, target, ignList);
    }

    public <T> T cpTo(Object source, T target, String... ignList) {
        c(source, target, ignList);
        return (T) target;
    }


    /**
         * COPY 属性(对象与MAP通用)
         *
         * @param source  原类
         * @param target  目标类
         * @param ignList 忽略列表
         */
    public static void copy(Object source, Object target, String... ignList) {
        BeanMapUtils.builder().c(source, target, ignList);
    }

    private static void toPojo(Object source, Object target, String[] ignList) {
        Map<String, Field> targetFieldMap = getFieldCache(target);
        Map<String, Field> sourceFieldMap = getFieldCache(source);
        if (isEmpty(sourceFieldMap) || isEmpty(targetFieldMap)) {
            throw new RuntimeException("targetFieldMap OR sourceFieldList is EMPTY !");
        }else {
            Map<String, Field> fuzzyFieldMap = new HashMap<>(targetFieldMap.size());
            if (spell_fuzzy_match) {
                String className = target.getClass().isMemberClass() ? target.getClass().getName() : target.getClass().getCanonicalName();
                fuzzyFieldMap = buildFuzzyFiedldMap(targetFieldMap,"fuzzy"+className);
            }
            //执行copy
            List<String> skipList = new ArrayList<>();
            if (null != ignList && ignList.length > 0) {
                skipList = Arrays.asList(ignList);
            }
            for (String sFieldName : sourceFieldMap.keySet()) {
                if (isSkipAttr(skipList, sFieldName)) continue;
                Object value = getFieldValue(source, sourceFieldMap.get(sFieldName));
                if (skip_null && null == value) continue; //跳过空值
                Field field = getFidldBySpell(spell_fuzzy_match ? fuzzyFieldMap : targetFieldMap, sFieldName);
                if (null != field) {
                    setFieldValue(target, field, value);
                }
            }
        }
    }

    /**
     * 要求做模糊匹配时，预先对目标类的字段进行格式转换
     * @param targetMap 目标 fieldMap
     * @param cacheKey  缓存 KEY
     * @return
     */
    private static Map<String, Field> buildFuzzyFiedldMap(Map<String, Field> targetMap,String cacheKey) {
        Map<String, Field> fuzzyFieldMap = CacheManager.getCacheVol(cacheKey);
        if (isEmpty(fuzzyFieldMap)) {
            fuzzyFieldMap = new HashMap<>(targetMap.size());
            for (String key : targetMap.keySet()) {
                Field v = targetMap.get(key);
                fuzzyFieldMap.put(StringKit.delSpellStyle(key), v);
            }
            CacheManager.buildCache(cacheKey, fuzzyFieldMap, Cache.ONE_HOUR);
        }
        return fuzzyFieldMap;
    }


    private static void toMap(Object source, Map<String, Object> targetMap, String... ignList) {
        Map<String,Field> sourceFieldMap = getFieldCache(source);
        if (isEmpty(sourceFieldMap)) {
            throw new RuntimeException("sourceFieldMap is EMPTY !");
        }else {
            //执行copy
            List<String> skipList = new ArrayList<>();
            if (null != ignList && ignList.length > 0) {
                skipList = Arrays.asList(ignList);
            }
            for (String sFieldName : sourceFieldMap.keySet()) {
                if (isSkipAttr(skipList, sFieldName)) continue;
                Object value = getFieldValue(source, sourceFieldMap.get(sFieldName));
                if (skip_null && null == value) continue;
                targetMap.put(formatSpell(sFieldName), value);
            }
        }
    }

    /**
     * 取得对象缓存的属性结构
     * @param pojo
     * @return
     */
    public static Map<String, Field> getFieldCache(Object pojo) {
        Class clz = pojo.getClass();
        String pojoName =  clz.getCanonicalName();
        pojoName = null == pojoName?clz.getName():pojoName;
        Map<String, Field> fieldMap = CacheManager.getCacheVol(pojoName);
        if (isEmpty(fieldMap)) {
            fieldMap = getFields(clz);
            if (isNotEmpty(fieldMap)) {
                if (cache_pojo) {
                    CacheManager.buildCache(pojoName, fieldMap, Cache.ONE_HOUR);
                }
            }
        }
        return fieldMap;
    }

    private static String formatSpell(String key) {
        if(to_underline){
            return StringKit.toUnderline(key);
        }
        if(to_camel){
            return StringKit.toCamel(key);
        }
        return key;
    }

    private static void fromMap(Map<String, Object> sourceMap, Object target, String... ignList) {
        Map<String,Field> targetFieldMap = getFieldCache(target);
        if (isEmpty(targetFieldMap)) {
            throw new FluentSqlException("trageFieldList is EMPTY !");
        }
        List<String> skipList = new ArrayList<>();
        if (null != ignList && ignList.length > 0) {
            skipList = Arrays.asList(ignList);
        }
        for (String key : sourceMap.keySet()) {
            if (isSkipAttr(skipList, key)) continue;
            Object val = sourceMap.get(key);
            if(skip_null && null==val) continue;
            Field field = getFidldBySpell(targetFieldMap, key);
            if (null != field) {
                setFieldValue(target, field, val);
            }
        }
    }

    private static boolean isSkipAttr(List<String> filterList, String currentAttr) {
        if(null == filterList || filterList.size() ==0) return false;
        for (String name : filterList) {
            if (name.equals(currentAttr)) return true;
        }
        return false;
    }


    private static Field getFidldBySpell(Map<String,Field> fieldMap, String key) {
        Field field ;
        field = fieldMap.get(key);
        if(null != field) return field;
        if (spell_fuzzy_match) {
            key = StringKit.delSpellStyle(key);
            field = fieldMap.get(key);
            if(null != field) return field;
        }else {
            if (to_camel) {
                key = StringKit.toCamel(key);
                field = fieldMap.get(key);
                if(null != field) return field;
            }
            if (to_underline) {
                key = StringKit.toUnderline(key);
                field = fieldMap.get(key);
                if(null != field) return field;
            }
        }
        return fieldMap.get(key);
    }


    private static Map<String, Field> getFields(Class clz) {
        Map<String, Field> fieldMap = new HashMap<>(31);
        getFieldsIter(clz, fieldMap);
        return fieldMap;
    }

    /**
     * 遍历取得类的属性（包含父类属性）
     *
     * @param clz
     * @param fieldMap
     */
    private static void getFieldsIter(Class clz, Map<String, Field> fieldMap) {
        Class nextClass = clz;
        while (nextClass != Object.class) {
            Field[] fields = nextClass.getDeclaredFields();
            for (Field field : fields) {
                int modifier = field.getModifiers();
                if (Modifier.isFinal(modifier)) continue;
                if (Modifier.isStatic(modifier)) continue;
                if (skip_transient && Modifier.isTransient(modifier)) continue;
                if (findIgnoreAnno(field)) continue;
                fieldMap.put(field.getName(), field);
            }
            nextClass = nextClass.getSuperclass();
        }
    }

    /**
     * 遍历取得类的所有方法
     *
     * @param clz
     * @param cache
     */
    public static Set<Method> getAllMethod(Class clz, Set<Method> cache) {
        Method[] methods = clz.getDeclaredMethods();
        for (Method method : methods) {
            if (Modifier.isFinal(method.getModifiers())) continue;
            if (Modifier.isStatic(method.getModifiers())) continue;
            cache.add(method);
        }
        if (null != clz.getSuperclass() && (!clz.getSuperclass().getName().equalsIgnoreCase("java.lang.Object"))) {
            getAllMethod(clz.getSuperclass(), cache);
        }

        return cache;
    }

    private static boolean findIgnoreAnno(Field field) {
        Annotation[] annoList = field.getAnnotations();
        for (Annotation annotation : annoList) {
            String annoName = annotation.annotationType().getSimpleName();
            if (skip_json_ignore && "JsonIgnore".equalsIgnoreCase(annoName)) return true;
            if (skip_json_reference && "JsonBackReference".equalsIgnoreCase(annoName)) return true;
            if (skip_transient && "transient".equalsIgnoreCase(annoName)) return true;
        }
        return false;
    }

    /**
     * 按规则过滤方法
     *
     * @param method
     * @return
     */
    private static boolean isSkipMethod(Method method) {
        if (Modifier.isStatic(method.getModifiers())) return true;
        if (Modifier.isFinal(method.getModifiers())) return true;
        if (method.getName().equalsIgnoreCase("toString")) return true;
        if (method.getName().equalsIgnoreCase("hashCode")) return true;
        return false;
    }

    private static boolean isTransientAnno(Annotation[] annotations) {
        for (Annotation annotation : annotations) {
            if ("transient".equalsIgnoreCase(annotation.annotationType().getSimpleName())) return true;
        }
        return false;
    }

    /**
     * 对属性设值
     *
     * @param targetInst  目标类实例,不是 xx.class
     * @param tField 目标字段
     * @param fieldValue
     */
    public static void setFieldValue(Object targetInst, Field tField, Object fieldValue) {
        try {
            tField.setAccessible(true);
            if (format_val) {
                if(String.class ==tField.getType()){//目标是String类型,直接传值
                    tField.set(targetInst, String.valueOf(fieldValue));
                }else {
                    tField.set(targetInst, getValOfBaseType(tField.getType(),fieldValue));
                }
            }else {
                tField.set(targetInst, fieldValue);
            }
        } catch (IllegalAccessException e) {
            throw new FluentSqlException(e);
        }
    }

    /**
     * 取得属性的值
     *
     * @param targetClz 目标类
     * @param field
     * @param <T>
     * @return
     */
    public static <T> T getFieldValue(Object targetClz, Field field) {
        try {
            field.setAccessible(true);
            return (T) field.get(targetClz);
        } catch (IllegalAccessException e) {
            throw new FluentSqlException(e);
        }
    }

    /**
     * 给目标字段传值
     * @param c 目标字段
     * @param v 值
     * @return
     */
    public static Object getValOfBaseType(Class<?> c, Object v) {
        if (null == v) return v;
        Class vc = v.getClass();
        String vs = String.valueOf(v);
        if(String.class == c) return vs;
        if (!isSameClz(c,vc) && (c == Integer.class || int.class == c))
            return Integer.parseInt(vs);
        else if (!isSameClz(c,vc) && (c == long.class || c == Long.class))
            return Long.parseLong(vs);
        else if (!isSameClz(c,vc) && (c == float.class || c == Float.class))
            return Float.parseFloat(vs);
        else if (!isSameClz(c,vc) && (c == double.class || c == Double.class)) {
            return Double.parseDouble(vs);
        }else if (!isSameClz(c,vc) && (c == BigDecimal.class)) {
            return BigDecimal.valueOf(Double.valueOf(vs));
        }
        //日期做特殊转换
        else if (!isSameClz(c,vc) && (c == Date.class)) {
            if(vc == LocalDateTime.class){
               return Date.from(((LocalDateTime) v).atZone(ZoneId.systemDefault()).toInstant());
            }
            if(vc == Date.class){
                return v;
            }
            if(vc == DateTime.class){
                return ((DateTime)v).toDate();
            }
            return DateUtils.toDate(vs);
        } else if (!isSameClz(c,vc) && (c == LocalDateTime.class)) {
            if(vc == LocalDateTime.class){
                return v;
            }
            if(vc == Date.class){
                return LocalDateTime.ofInstant(((Date) v).toInstant(), ZoneId.systemDefault());
            }
            if(vc == DateTime.class){
                return LocalDateTime.parse(v.toString(),ISO_OFFSET_DATE_TIME);
            }
            return DateUtils.toDate(vs);
        } else if (!isSameClz(c,vc) && (c == DateTime.class)) {
            if(vc == LocalDateTime.class){
                return DateTime.parse(v.toString());
            }
            if(vc == Date.class){
                return DateTime.parse(((Date) v).toInstant().toString());
            }
            if(vc == DateTime.class){
                return v;
            }
            return DateUtils.toDate(vs);
        }
        return v;
    }

    private static boolean isSameClz(Class k, Class v) {
        if(k==v) return true;
        if(k.isAssignableFrom(v) ||v.isAssignableFrom(k)) return true;
        return false;
    }

    private static boolean isEmpty(Map map) {
        if(map == null) return true;
        return map.isEmpty();
    }

    private static boolean isNotEmpty(Map map) {
        return !isEmpty(map);
    }

    public static Map<String, Object> newSortMap(){
        // 使用TreeMap并提供一个Comparator用于排序key
        Map<String, Object> sortedMap = new TreeMap<>(new Comparator<String>() {
            @Override
            public int compare(String k1, String k2) {
                // 对于字符串key升序排序
                return k1.compareTo(k2);
            }
        });
        return sortedMap;
    }




    public static void main(String[] args) {

        class Foo {
            private Long id;
            private String userName;
            private Date createTime;
            private LocalDateTime updateTime;
            private DateTime jodaTime;
            private BigDecimal money;
            private  Double age;

            public Long getId() {
                return id;
            }

            public void setId(Long id) {
                this.id = id;
            }

            public String getUserName() {
                return userName;
            }

            public void setUserName(String userName) {
                this.userName = userName;
            }

            public Date getCreateTime() {
                return createTime;
            }

            public void setCreateTime(Date createTime) {
                this.createTime = createTime;
            }

            public LocalDateTime getUpdateTime() {
                return updateTime;
            }

            public void setUpdateTime(LocalDateTime updateTime) {
                this.updateTime = updateTime;
            }

            public BigDecimal getMoney() {
                return money;
            }

            public void setMoney(BigDecimal money) {
                this.money = money;
            }

            public Double getAge() {
                return age;
            }

            public void setAge(Double age) {
                this.age = age;
            }

            public DateTime getJodaTime() {
                return jodaTime;
            }

            public void setJodaTime(DateTime jodaTime) {
                this.jodaTime = jodaTime;
            }

            @Override
            public String toString() {
                final StringBuffer sb = new StringBuffer("Foo{");
                sb.append("id=").append(id);
                sb.append(", userName='").append(userName).append('\'');
                sb.append(", createTime=").append(createTime);
                sb.append(", updateTime=").append(updateTime);
                sb.append(", jodaTime=").append(jodaTime);
                sb.append(", money=").append(money);
                sb.append(", age=").append(age);
                sb.append('}');
                return sb.toString();
            }
        }

        class Du{
            private Integer id;
            private String user_name;

            public Integer getId() {
                return id;
            }

            public void setId(Integer id) {
                this.id = id;
            }

            public String getUser_name() {
                return user_name;
            }

            public void setUser_name(String user_name) {
                this.user_name = user_name;
            }

            @Override
            public String toString() {
                final StringBuilder sb = new StringBuilder("Du{");
                sb.append("id=").append(id);
                sb.append(", user_name='").append(user_name).append('\'');
                sb.append('}');
                return sb.toString();
            }
        }

        class Boo {
            private Integer id;
            private LocalDateTime createTime;
            private Date updateTime;
            private LocalDateTime jodaTime;
            private String money;
            private double age;

            public Integer getId() {
                return id;
            }

            public void setId(Integer id) {
                this.id = id;
            }
            public LocalDateTime getCreateTime() {
                return createTime;
            }

            public void setCreateTime(LocalDateTime createTime) {
                this.createTime = createTime;
            }

            public Date getUpdateTime() {
                return updateTime;
            }

            public void setUpdateTime(Date updateTime) {
                this.updateTime = updateTime;
            }

            public String getMoney() {
                return money;
            }

            public void setMoney(String money) {
                this.money = money;
            }

            public double getAge() {
                return age;
            }

            public void setAge(double age) {
                this.age = age;
            }

            public LocalDateTime getJodaTime() {
                return jodaTime;
            }

            public void setJodaTime(LocalDateTime jodaTime) {
                this.jodaTime = jodaTime;
            }

            @Override
            public String toString() {
                final StringBuffer sb = new StringBuffer("Boo{");
                sb.append("id=").append(id);
                sb.append(", createTime=").append(createTime);
                sb.append(", updateTime=").append(updateTime);
                sb.append(", jodaTime=").append(jodaTime);
                sb.append(", money='").append(money).append('\'');
                sb.append(", age=").append(age);
                sb.append('}');
                return sb.toString();
            }
        }
        Foo foo = new Foo();
        Foo foo2 = new Foo();
        Foo foo3 = new Foo();
        Map<String, Object> tMap = new HashMap<>();

        foo.setId(1L);
        foo.setUserName("alice");
        foo.setAge(18.00);
        foo.setCreateTime(new Date());
        foo.setUpdateTime(LocalDateTime.now());
        foo.setJodaTime(DateTime.now());
        foo.setMoney(new BigDecimal("23.00"));
        System.err.println("foo1= " + foo.toString());

        BeanMapUtils.copy(foo, foo2);
        System.err.println("foo1 ->foo2 = " + foo2.toString());
        //
        BeanMapUtils.copy(foo, tMap);
        System.err.println("foo -> tMap = " + tMap.toString());

        BeanMapUtils.copy(tMap, foo3);
        System.err.println("tMap -> foo = " + foo3.toString());

        Boo boo = new Boo();
        BeanMapUtils.builder().copy(foo, boo);
        System.err.println("foo -> boo = " + boo.toString());

        boo.setId(999);
        BeanMapUtils.copy(boo, foo);
        System.err.println("boo -> foo = " + foo.toString());


        BeanMapUtils.copy(tMap, boo);
        System.err.println("tMap -> boo = " + boo.toString());

        Du du = new Du();

        BeanMapUtils.fuzzy().c(foo,du);
        System.err.println("foo -> du = " + du.toString());

        Bc.fuzzy().c(foo,du);
        System.err.println("foo -> du = " + du.toString());



    }
}
